/**
 * 
 */
package smallChanges.statements;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import net.sourceforge.pmd.ast.ASTBlock;
import net.sourceforge.pmd.ast.ASTBlockStatement;
import net.sourceforge.pmd.ast.ASTBreakStatement;
import net.sourceforge.pmd.ast.ASTCatchStatement;
import net.sourceforge.pmd.ast.ASTClassOrInterfaceBody;
import net.sourceforge.pmd.ast.ASTClassOrInterfaceBodyDeclaration;
import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.ast.ASTContinueStatement;
import net.sourceforge.pmd.ast.ASTDoStatement;
import net.sourceforge.pmd.ast.ASTEnumBody;
import net.sourceforge.pmd.ast.ASTEnumConstant;
import net.sourceforge.pmd.ast.ASTEnumDeclaration;
import net.sourceforge.pmd.ast.ASTExplicitConstructorInvocation;
import net.sourceforge.pmd.ast.ASTExpression;
import net.sourceforge.pmd.ast.ASTExtendsList;
import net.sourceforge.pmd.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.ast.ASTFinallyStatement;
import net.sourceforge.pmd.ast.ASTForStatement;
import net.sourceforge.pmd.ast.ASTFormalParameter;
import net.sourceforge.pmd.ast.ASTIfStatement;
import net.sourceforge.pmd.ast.ASTImplementsList;
import net.sourceforge.pmd.ast.ASTImportDeclaration;
import net.sourceforge.pmd.ast.ASTInitializer;
import net.sourceforge.pmd.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.ast.ASTPackageDeclaration;
import net.sourceforge.pmd.ast.ASTStatement;
import net.sourceforge.pmd.ast.ASTSwitchLabel;
import net.sourceforge.pmd.ast.ASTSwitchStatement;
import net.sourceforge.pmd.ast.ASTSynchronizedStatement;
import net.sourceforge.pmd.ast.ASTTryStatement;
import net.sourceforge.pmd.ast.ASTType;
import net.sourceforge.pmd.ast.ASTTypeDeclaration;
import net.sourceforge.pmd.ast.ASTWhileStatement;
import net.sourceforge.pmd.ast.JavaParserVisitorAdapter;
import net.sourceforge.pmd.ast.Node;
import net.sourceforge.pmd.ast.SimpleJavaNode;
import smallChanges.Tokenization.SCToken;
import smallChanges.Tokenization.TokenSet;
import smallChanges.util.SCConstants;

/**
 * @author jspacco
 *
 */
public class BlockStatementVisitor extends JavaParserVisitorAdapter implements SCConstants
{
    protected TokenSet tokenSet;
    // The current class declaration we are working with
    // Necessary to put inner classes into the correct place
    // since inner classes (anonymous or otherwise) can show up
    // as children of an ASTBlockStatement INSIDE a method, as well as outside
    // a method.  Since ASTBlockStatement will be passed a StatementList
    // for collecting statements, we need some other means to track the 
    // current class declaration in order to nest the inner classes
    // correctly.  So we use a stack!
    private Stack<ClassDeclaration> classStack=new Stack<ClassDeclaration>();
    
	public BlockStatementVisitor(TokenSet tokenSet) {
		this.tokenSet=tokenSet;
	}
	
	private static class GhettoStatementContainer
	{
		// Stupid container class because I can't figure out how to make 
		// the visitor pattern do exactly what I want it to do...
		public Statement statement;
	}
	
	
	
    @Override
    public Object visit(ASTEnumBody node, Object data)
    {
        // TODO Auto-generated method stub
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTEnumConstant node, Object data)
    {
        // TODO Auto-generated method stub
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTEnumDeclaration node, Object data)
    {
        // TODO Auto-generated method stub
        return super.visit(node, data);
    }

    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTSwitchStatement, java.lang.Object)
     */
    @Override
    public Object visit(ASTSwitchStatement node, Object data)
    {
        if (data instanceof GhettoStatementContainer) {
            GhettoStatementContainer c=(GhettoStatementContainer)data;
            
            SwitchStatement switchStatement=new SwitchStatement(tokenSet);
            SwitchHeader header=new SwitchHeader(tokenSet);
            header.setBeginCoordsFromNode(node);
            switchStatement.setBeginCoordsFromNode(node);
            // get the first child of the switch statement
            // this is the expression we're switching on
            // use this to decide where the switch header ends
            // also remember to grab the paren after the switch expression
            ASTExpression expression=(ASTExpression)node.jjtGetChild(0);
            header.setEndCoordsFromNode(expression);
            header.expandByOneFollowingToken();
            switchStatement.setHeaderStmt(header);
            
            // if there is no body (i.e. the only child of the switch is the expression)
            // then set the body to the empty list
            if (node.jjtGetNumChildren() < 2) {
                switchStatement.setBody(StatementList.EMPTY_STATEMENT_LIST);
                switchStatement.setEndCoordsFromNode(node);
                c.statement=switchStatement;
                return c;
            }
            // otherwise find the body
            StatementList body=new StatementList(tokenSet);
            body.setStmtType(SWITCH_BODY);
            // set the beginning of the body as the first token after the switch header
            body.setBeginCoordsFromToken(tokenSet.getFollowingTokens(header.getTokensInRange().getLast()).getFirst());
            // set the end of the body as the end of the last node of the switch body
            body.setEndCoordsFromNode((SimpleJavaNode)node.jjtGetChild(node.jjtGetNumChildren()-1));
            
            // now visit the body
            //node.jjtAccept(this, body);
            node.childrenAccept(this, body);
            switchStatement.setBody(body);
            switchStatement.setEndLine(body.getEndLine());
            switchStatement.setEndColumn(body.getEndColumn());
            
            c.statement=switchStatement;
            return c;
        }
        return super.visit(node, data);
    }

    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTSwitchLabel, java.lang.Object)
     */
    @Override
    public Object visit(ASTSwitchLabel node, Object data)
    {
        if (data instanceof StatementList) {
            StatementList list=(StatementList)data;
            SingleStatement stmt=new SingleStatement(CASE_STATEMENT, tokenSet);
            stmt.setCoordinatesFromNode(node);
            list.addStatement(stmt);
            return list;
        }
        return super.visit(node, data);
    }



    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTTryStatement, java.lang.Object)
     */
    @Override
    public Object visit(ASTTryStatement node, Object data)
    {
        if (data instanceof GhettoStatementContainer) {
            GhettoStatementContainer container=(GhettoStatementContainer)data;
            
            TryStatement tryStatement=new TryStatement(tokenSet);
            tryStatement.setCoordinatesFromNode(node);
            
            // The "header" is just the "try" keyword that starts a try-catch block
            // This is the first token in the try statement
            TryStatementHeader header=new TryStatementHeader(tokenSet);
            header.setCoordinatesFromToken(tryStatement.getTokensInRange().getFirst());
            tryStatement.setHeaderStmt(header);
            
            // recover the "try" part of the header
            for (int ii=0; ii<node.jjtGetNumChildren(); ii++) {
                Node n=node.jjtGetChild(ii);
                if (n instanceof ASTBlock) {
                    ASTBlock blockNode=(ASTBlock)n;
                    StatementList body=new StatementList(tokenSet);
                    blockNode.jjtAccept(this, body);
                    tryStatement.setBody(body);
                } else if (n instanceof ASTCatchStatement) {
                    ASTCatchStatement catchNode=(ASTCatchStatement)n;
                    CatchStatement catchStatement=new CatchStatement(tokenSet);
                    catchNode.jjtAccept(this, catchStatement);
                    tryStatement.addCatchStatement(catchStatement);
                } else if (n instanceof ASTFinallyStatement) {
                    ASTFinallyStatement finallyNode=(ASTFinallyStatement)n;
                    // first, recover the "finally" statement
                    // this is always the first token of the entire finally block
                    SingleStatement finallyStatement=
                        new SingleStatement(FINALLY_KEYWORD, tokenSet);
                    finallyStatement.setCoordinatesFromToken(tokenSet.getAllTokensComprisingNode(finallyNode).getFirst());

                    StatementList finallyBlock=new StatementList(tokenSet);
                    finallyBlock.setCoordinatesFromNode(finallyNode);
                    finallyBlock.addStatement(finallyStatement);
                    
                    // get the statement list of the statements in the finally block
                    finallyNode.jjtAccept(this, finallyBlock);
                    tryStatement.setFinallyBlock(finallyBlock);
                }
            }
            container.statement=tryStatement;
            return container;
        }
        return super.visit(node, data);
    }

    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTCatchStatement, java.lang.Object)
     */
    @Override
    public Object visit(ASTCatchStatement node, Object data)
    {
        if (data instanceof CatchStatement) {
            CatchStatement catchStatement=(CatchStatement)data;
            catchStatement.setCoordinatesFromNode(node);
            // Extract the header info
            CatchStatementHeader header=new CatchStatementHeader(tokenSet);
            header.setBeginCoordsFromNode(node);
            
            // TODO get the meta-info of the catch block's formal parameters here...
            // get the catch block's formal parameter
            // this MUST exist for this to be a valid ASTCatchStatement
            // so I don't need to check the return value for null
            ASTFormalParameter formalParam=node.getFirstChildOfType(ASTFormalParameter.class);
            header.setEndCoordsFromNode(formalParam);
            // and I need to expand by one token in order to capture the closing paren after
            // the catch block's formal param (exception type and name)
            header.expandByOneFollowingToken();
            catchStatement.setHeaderStmt(header);
            
            // extract the body
            StatementList body=new StatementList(tokenSet);
            body.setStmtType(CATCH_BODY);
            node.childrenAccept(this, body);
            
            catchStatement.setBody(body);
            return catchStatement;
        }
        return super.visit(node, data);
    }
    
    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTSynchronizedStatement, java.lang.Object)
     */
    @Override
    public Object visit(ASTSynchronizedStatement node, Object data)
    {
        if (data instanceof GhettoStatementContainer) {
            GhettoStatementContainer c=(GhettoStatementContainer)data;
            
            // Extract the synchronized header and variable
            SynchronizedHeader synchHeader=new SynchronizedHeader(tokenSet);
            ConditionalStatementVisitor visitor=new ConditionalStatementVisitor(tokenSet);
            node.jjtAccept(visitor, synchHeader);
            
            // get the body of the synchronized block
            StatementList synchBody=new StatementList(tokenSet);
            synchBody.setStmtType(SYNCHRONIZED_BODY);
            // visit the block
            // synchronized blocks always have curly braces
            // and therefore always start with ASTBlock
            node.childrenAccept(this, synchBody);

            // set the coordinates of the entire statement
            // set the header condition and the then-body
            SynchronizedStatement synchStmt=new SynchronizedStatement(tokenSet);
            synchStmt.setCoordinatesFromNode(node);
            synchStmt.setHeaderStmt(synchHeader);
            synchStmt.setBody(synchBody);
            
            // Using the ghetto statement container to pass return values back up
            // the call chain
            c.statement=synchStmt;
            
            return c;
        }
        return super.visit(node, data);
    }
    
    @Override
    public Object visit(ASTImportDeclaration node, Object data)
    {
        if (data instanceof ParsedSourceFile) {
            ParsedSourceFile parsedSourceFile=(ParsedSourceFile)data;
            ImportStatement stmt=new ImportStatement(tokenSet);
            stmt.setCoordinatesFromNode(node);
            parsedSourceFile.addImportStatement(stmt);
        }
        return super.visit(node, data);
    }
    
    

    @Override
    public Object visit(ASTTypeDeclaration node, Object data)
    {
        if (node.jjtGetNumChildren()==0) {
            // This only happens when the file has semi-colons
            // after the final }
            return null;
        }
        if (data instanceof ParsedSourceFile) {
            
            ParsedSourceFile parsedSourceFile=(ParsedSourceFile)data;
            ClassDeclaration classDeclaration=new ClassDeclaration();
            ClassHeader header=new ClassHeader(tokenSet);
            header.setBeginCoordsFromNode(node);
            classDeclaration.setHeader(header);
            
            classStack.add(classDeclaration);
            node.childrenAccept(this, classDeclaration);
            classStack.pop();
            parsedSourceFile.addTopLevelClass(classDeclaration);
            return parsedSourceFile;
        }
        //TODO delete
//        if (data instanceof List) {
//            List<ClassDeclaration> list=(List<ClassDeclaration>)data;
//            ClassDeclaration classDeclaration=new ClassDeclaration();
//            ClassHeader header=new ClassHeader(tokenSet);
//            header.setBeginCoordsFromNode(node);
//            classDeclaration.setHeader(header);
//            
//            classStack.add(classDeclaration);
//            node.childrenAccept(this, classDeclaration);
//            classStack.pop();
//            
//            // add the completed top-level class to our list of results
//            list.add(classDeclaration);
//            return list;
//        }
        return super.visit(node, data);
            
    }

    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTExtendsList, java.lang.Object)
     */
    @Override
    public Object visit(ASTExtendsList node, Object data)
    {
        if (data instanceof ClassDeclaration) {
            // bump ahead the end of our class header
            // TODO collect meta-information?
            ClassDeclaration classDeclaration=(ClassDeclaration)data;
            classDeclaration.getHeader().setEndCoordsFromNode(node);
        }
        return super.visit(node, data);
    }

    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTImplementsList, java.lang.Object)
     */
    @Override
    public Object visit(ASTImplementsList node, Object data)
    {
        if (data instanceof ClassDeclaration) {
            ClassDeclaration classDeclaration=(ClassDeclaration)data;
            classDeclaration.getHeader().setEndCoordsFromNode(node);
        }
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTClassOrInterfaceBody node, Object data)
    {
        if (data instanceof ClassDeclaration) {
            ClassDeclaration classDecl=(ClassDeclaration)data;
            // The body of the class STARTS here
            // This is where the class header ENDS
            classDecl.getHeader().setEndLine(node.getBeginLine());
            classDecl.getHeader().setEndColumn(node.getBeginColumn());
            classDecl.getHeader().setTokensFromRange();
            // set the end coordinates of the class based on the body
            classDecl.setEndLine(node.getEndLine());
            classDecl.setEndColumn(node.getEndColumn());
        }
        return super.visit(node, data);
    }
    private String packageName;
    @Override
    public Object visit(ASTPackageDeclaration node, Object data)
    {
        packageName=((SimpleJavaNode)node.jjtGetChild(0)).getImage();
        return null;
    }



    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration, java.lang.Object)
     */
    @Override
    public Object visit(ASTClassOrInterfaceDeclaration node, Object data)
    {
        // If the immediate parent is ASTTypeDeclaration, then this is the
        // top-level class in the file and we can simply continue.
        // Otherwise, this is an inner-class, and we need to create a new
        // ClassDeclaration to hold the statements of the inner class.
        if( data instanceof ClassDeclaration ) {
            ClassDeclaration classDeclaration=(ClassDeclaration)data;
            if (node.jjtGetParent() instanceof ASTTypeDeclaration) {
                // prepend the package name
                if (packageName!=null)
                    classDeclaration.setName(packageName+"."+node.getImage());
                else
                    classDeclaration.setName(node.getImage());
            } else {
                ClassDeclaration innerClass=new ClassDeclaration();
                ClassDeclaration parent=classStack.peek();
                if (node.jjtGetParent() instanceof ASTBlockStatement)
                    innerClass.setName(node.getImage());
                else
                    innerClass.setName(parent.getName()+"$"+node.getImage());
                
                ClassHeader header=new ClassHeader(tokenSet);
                header.setBeginCoordsFromNode(node);
                innerClass.setHeader(header);

                classStack.push(innerClass);
                node.childrenAccept(this, innerClass);
                classStack.pop();

                classDeclaration.addClassDeclaration(innerClass);
                return classDeclaration;
            }
        }
        return super.visit(node, data);
    }

    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTInitializer, java.lang.Object)
     */
    @Override
    public Object visit(ASTInitializer node, Object data)
    {
        // static initializer
        if (data instanceof ClassDeclaration) {
            ClassDeclaration classDeclaration=(ClassDeclaration)data;
            

            // Recover the "static" keyword from the
            // list of static initializer statements.  Luckily, the keyword static
            // shows up as the first token in the ASTInitializer
            // XXX Setting "static" as the first statement in the statement list;
            // could maybe create a new statement with the "static" keyword
            // as the header
            if (classDeclaration.getStaticInitializer()==StatementList.EMPTY_STATEMENT_LIST) {
                StatementList initializer=new StatementList(tokenSet);
                initializer.setStmtType(STATIC_INITIALIZER);
                TokenSet following=tokenSet.getAllTokensComprisingNode(node);
                SingleStatement staticKeyword=new SingleStatement(tokenSet);
                staticKeyword.setCoordinatesFromToken(following.getFirst());
                initializer.addStatement(staticKeyword);
                // collect all the statements in the static initializer
                node.childrenAccept(this, initializer);
                classDeclaration.setStaticInitializer(initializer);
            } else {
                // it's possible to have more than one static initializer
                // we're going to merge them together by passing down the
                // 
                node.childrenAccept(this, classDeclaration.getStaticInitializer());
            }
        }
        return super.visit(node, data);
    }
    
    private static Set<String> expectedModifierTokens=null;

    @Override
    public Object visit(ASTFieldDeclaration node, Object data)
    {
        if (data instanceof ClassDeclaration) {
        	// TODO handle inner classes declared as fields
            ClassDeclaration classDeclaration=(ClassDeclaration)data;
            ASTType typeNode = node.getFirstChildOfType(ASTType.class);
            TypeVisitor typeVisitor = new TypeVisitor(tokenSet);
            SCType scType = new SCType(tokenSet);
            typeNode.jjtAccept(typeVisitor, scType);
            
            VariableVisitor varVisitor = new VariableVisitor(tokenSet);
            varVisitor.setType(scType);
            List<FieldDeclarationStatement> fdsList = new LinkedList<FieldDeclarationStatement>();
            
            node.jjtAccept(varVisitor, fdsList);
            
            //
            // TODO 
            // handle gawd-awful C-style array decls:
            // public static int x[], y[];
            //

            if (expectedModifierTokens==null) {
                expectedModifierTokens=new HashSet<String>();
                
            }
            
            // Figure out how many tokens we need to add to the implicit
            // tokens TokenSet.
            int numTokens=0;
            if (node.isPublic() || node.isPrivate() || node.isProtected()) {
                numTokens++;
            }
            if (node.isFinal()) {
                numTokens++;
            }
            if (node.isStatic()) {
                numTokens++;
            }
            if (node.isVolatile()) {
                numTokens++;
            }
            if (node.isTransient()) {
                numTokens++;
            }
            TokenSet implicitTokens=new TokenSet();
            SCToken t=null;
            for (int ii=0; ii<numTokens; ii++) {
                if (t==null) {
                    // TODO doesn't compile!
//                    if (!tokenSet.isPrecedingTokenImageInSet(set, node)) {
//                        break;
//                    }
                    t=tokenSet.getOnePrecedingToken(node);
                } else {
                    // TODO doesn't compile
//                    if (!tokenSet.isPrecedingTokenImageInSet(set, t)) {
//                        break;
//                    }
                    t=tokenSet.getOnePrecedingToken(t);
                }
                implicitTokens.add(t);
            }
            
            for (FieldDeclarationStatement fds : fdsList) {
            	// set the visibility and modifiers
                fds.setVisibility(SCVisibility.createSCVisibility(node));
                fds.setModifiers(SCModifiers.createSCModifiers(node));
                // add the implicit tokens to each field
                if (implicitTokens.size()>0) {
                    // XXX We're creating an over-lapping set of tokens
                    // for example:
                    // int x=10, y=20;
                    // both include the "int" token and neither include the , or ;
                    // We may also make a coord range that doesn't include 
                    // all the tokens inside it (i.e. a range with gaps)
                    fds.getTokensInRange().addAll(implicitTokens);
                    fds.setBeginLine(implicitTokens.getFirst().getBeginLine());
                    fds.setBeginColumn(implicitTokens.getFirst().getBeginColumn());
                }
            	classDeclaration.addFieldDeclaration(fds);
            }
            return classDeclaration;
        }
        return super.visit(node, data);
    }

    public Object visit(ASTStatement node, Object data)
	{
		if (data instanceof StatementList) {
		    StatementList parent=(StatementList)data;
			// If immediate child is ASTBlock, just pass down the given StatementList
			// otherwise, this is in fact the statement that we want
		    // XXX assert there is only one child
		    assert(node.jjtGetNumChildren()==1);
		    if (node.jjtGetChild(0) instanceof ASTBlock) {
		        return super.visit(node, data);
			}
			
			// If there's eventually going to be an inner class, then
			// at this point we can assume it's an anonymous inner class
			// (because if it were a named inner class, we would have
			// already seen it below the ASTBlockStatement)
			if (node.containsChildOfType(ASTClassOrInterfaceBodyDeclaration.class))
			{
			    // XXX assert only one child
			    assert(node.jjtGetNumChildren()==1);
			    Node n=node.jjtGetChild(0);
			    if (
			            !(n instanceof ASTDoStatement) &&
			            !(n instanceof ASTIfStatement) &&
			            !(n instanceof ASTForStatement) &&
			            !(n instanceof ASTWhileStatement) &&
			            !(n instanceof ASTSwitchStatement) &&
			            !(n instanceof ASTSynchronizedStatement) &&
			            !(n instanceof ASTTryStatement)
			    )
			    {
			        // If we've gotten here, then this is an ASTStatement
			        // with no statements containing blocks inside of it
			        // and we will eventually get to an anonymous inner class.
			        // I believe it is now safe to treat this whole thing as
			        // an anonymous inner class.
			        // XXX synthetic placeholder
			        ClassDeclaration classDeclaration=new ClassDeclaration();
			        
			        // set the name of the new anonymous inner class
			        ClassDeclaration parentClass=classStack.peek();
                    parentClass.addClassDeclaration(classDeclaration);
                    classDeclaration.setName(parentClass.getNextAnonymousInnerClassName());
			        
			        ClassHeader header=new ClassHeader(tokenSet);
			        header.setBeginCoordsFromNode(node);
			        classDeclaration.setHeader(header);
			        classStack.push(classDeclaration);
			        node.childrenAccept(this, classDeclaration);
			        classStack.pop();
			        
			        return data;
			    } 
			    // otherwise, we need to pass down a ghetto container
			    // to collect the statements
			    GhettoStatementContainer c=new GhettoStatementContainer();
			    node.childrenAccept(this, c);
			    parent.addStatement(c.statement);
			    return parent;
			}
			
			// If immediate parent is ASTBlockStatement, then by definition
			// there are more blocks coming (since if there weren't, this
			// ASTStatement would already be part of a SingleStatement)
			// or an inner class declaration) so just pass on what we were given
			if (node.jjtGetParent() instanceof ASTBlockStatement) {
			    return super.visit(node, data);
			}
			
			// If the immediate parent is ASTIfStatement, ASTForStatement, 
			// or ASTWhileStatement, set the coordinates for this statement list
			// This also gets done in the ASTBlock, but it's possible that this
			// statement is only a single statement and isn't part of a block of statements
			if (node.jjtGetParent() instanceof ASTIfStatement ||
					node.jjtGetParent() instanceof ASTWhileStatement ||
					node.jjtGetParent() instanceof ASTForStatement ||
					node.jjtGetParent() instanceof ASTDoStatement 
					)
			{
				parent.setCoordinatesFromNode(node);
			}
			// assert only one child
			assert (node.jjtGetNumChildren()==1);
			SimpleJavaNode child=(SimpleJavaNode)node.jjtGetChild(0);
			if (child instanceof ASTBlock ||
			        child instanceof ASTDoStatement ||
			        child instanceof ASTIfStatement ||
			        child instanceof ASTForStatement ||
			        child instanceof ASTWhileStatement ||
			        child instanceof ASTTryStatement ||
			        child instanceof ASTSwitchStatement ||
			        child instanceof ASTSynchronizedStatement
			        )
			{
				// will eventually become either if/else, while, 
			    // for, do, switch, try, (at the next node I think?)
				GhettoStatementContainer c=new GhettoStatementContainer();

				node.childrenAccept(this, c);
				parent.addStatement(c.statement);
				return parent;
			}
			// Terminal statement (no more blocks inside it)
			Statement stmt=new SingleStatement(tokenSet);
			stmt.setCoordinatesFromNode(node);
			// look for embedded method calls inside statements
			MethodCallFinder mcf=new MethodCallFinder(tokenSet);
			node.jjtAccept(mcf, stmt);
			// add statement to the list
			parent.addStatement(stmt);
			return parent;
		}
		return super.visit(node, data);
	}
	
	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTBlockStatement, java.lang.Object)
	 */
	@Override
	public Object visit(ASTBlockStatement node, Object data)
	{
		if (data instanceof StatementList) {
			StatementList parent=(StatementList)data;
			// look for an inner class declared inside the method
			// yes, this is legal Java syntax, believe it or not
			// XXX assert only one child
			if (node.jjtGetChild(0) instanceof ASTClassOrInterfaceDeclaration) {
			    // method-scoped named inner class
			    // can't use node.containsChildOfType() because we need
			    // to differentiate between an anonymous inner class
			    // (which eventually yields ASTClassOrInterfaceDeclaration),
			    // and a named inner class declared inside a method
			    // (which immediately yields ASTClassOrInterfaceDeclaration).
			    // 
			    // XXX synthetic placeholder
			    ClassDeclaration parentClass=classStack.peek();
			    node.childrenAccept(this, parentClass);
			    // name for method-scoped inner class Name is:
			    // OuterClassName$1Name
			    ClassDeclaration innerClass=
			        parentClass.getInnerClasses().get(parentClass.getInnerClasses().size()-1);
			    innerClass.setName(parentClass.getNextMethodScopedInnerClassName(innerClass.getName()));
			    
			    return parent;
			}
			// If eventually we'll get to an anonymous inner class, 
			// and our child is ASTStatement, pass the buck along there
			// TODO can I pattern-match all the possible ways of
			// creating an anon. inner class in this statement?
			if (node.containsChildOfType(ASTClassOrInterfaceBody.class)) {
			    // XXX assert that there is only one child
			    Node n=node.jjtGetChild(0);
			    if (n instanceof ASTStatement) {
			        node.childrenAccept(this, parent);
			        return parent;
			    }
			    // true anonymous inner class
                // XXX synthetic placeholder
                ClassDeclaration classDeclaration=new ClassDeclaration();
                ClassHeader header=new ClassHeader(tokenSet);
                header.setBeginCoordsFromNode(node);
                classDeclaration.setHeader(header);
                // add new class to the stack so it can accumulate any inner classes
                classStack.push(classDeclaration);
                node.childrenAccept(this, classDeclaration);
                classStack.pop();
                ClassDeclaration parentClass=classStack.peek();
                // set name of anon. inner class
                classDeclaration.setName(parentClass.getNextAnonymousInnerClassName());
                
                // add anon. inner class to outer (parent) class
                parentClass.addClassDeclaration(classDeclaration);
                
                return parent;
            }
			// XXX containsChildOfType() is crazy-inefficient!
			// can we figure this out by simply looking at the grandchildren?
			Node grandNode=getGrandChild(node);
			if (
			        (grandNode instanceof ASTDoStatement) ||
			        (grandNode instanceof ASTIfStatement) ||
			        (grandNode instanceof ASTForStatement) ||
			        (grandNode instanceof ASTWhileStatement) ||
			        (grandNode instanceof ASTSwitchStatement) ||
			        (grandNode instanceof ASTSynchronizedStatement) ||
			        (grandNode instanceof ASTTryStatement)
			        )
            { 
				GhettoStatementContainer c=new GhettoStatementContainer();

				// can't do childrenAccept() since the ASTStatement below us
				// will be what takes over, and our visitor method for ASTStatement
				// always expects a StatementList, not a GhettoStatementContainer
				node.jjtAccept(this, c);
				//node.childrenAccept(this, c);
				parent.addStatement(c.statement);
				return parent;
			}
			// if we've got a block as a statement inside this block, then
			// we need to capture that with a new StatementList
			if (grandNode instanceof ASTBlock) {
			    StatementList list=new StatementList(tokenSet);
			    list.setStmtType(STATEMENT_LIST);
			    list.setCoordinatesFromNode(node);
			    node.childrenAccept(this, list);
			    parent.addStatement(list);
			    // XXX Do I return here?
			}
			// Terminal statement (no more blocks inside it)
			Statement stmt=new SingleStatement(tokenSet);
			stmt.setCoordinatesFromNode(node);
			if (grandNode instanceof ASTContinueStatement) {
			    stmt.setStmtType(CONTINUE);
            } else if (grandNode instanceof ASTBreakStatement) {
                stmt.setStmtType(BREAK);
            }
			// look for embedded method calls inside statements
            MethodCallFinder mcf=new MethodCallFinder(tokenSet);
            node.jjtAccept(mcf, stmt);
            // add statement to the list
			parent.addStatement(stmt);
			return parent;
		}
		return super.visit(node, data);
	}
	
	
	@Override
    public Object visit(ASTExplicitConstructorInvocation node, Object data)
    {
	    if (data instanceof StatementList) {
	        StatementList list=(StatementList)data;
	        Statement stmt=new SingleStatement(tokenSet);
	        stmt.setCoordinatesFromNode(node);
	        // look for embedded method calls inside statements
	        
            MethodCallFinder mcf=new MethodCallFinder(tokenSet);
            node.jjtAccept(mcf, stmt);
            // add statement to the list
	        list.addStatement(stmt);
	        return list;
	    }
        return super.visit(node, data);
    }



    /* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTBlock, java.lang.Object)
	 */
	@Override
	public Object visit(ASTBlock node, Object data)
	{
		if (data instanceof StatementList) {
			if (!(node.jjtGetParent() instanceof ASTSynchronizedStatement)) {
			    StatementList parent=(StatementList)data;
	            parent.setCoordinatesFromNode(node);
			}
		}
		return super.visit(node, data);
	}

	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTForStatement, java.lang.Object)
	 */
	@Override
	public Object visit(ASTForStatement node, Object data)
	{
		if (data instanceof GhettoStatementContainer) {
			GhettoStatementContainer c=(GhettoStatementContainer)data;
			
			// Extract the for statement header
			ForStatementHeader forHeader=new ForStatementHeader(tokenSet);
			ForStatementVisitor visitor=new ForStatementVisitor(tokenSet);
			node.jjtAccept(visitor, forHeader);
			
			StatementList list=new StatementList(tokenSet);
			list.setStmtType(FOR_BODY);
			super.visit(node, list);
			
			ForStatement forStmt=new ForStatement(tokenSet);
			// set coordinates of the compound statement for the ENTIRE for statement
			// (i.e. including the loop header as well as all statements in the loop body)
			forStmt.setCoordinatesFromNode(node);
			forStmt.setHeaderStmt(forHeader);
			forStmt.setBody(list);
			
			// Using the ghetto statement container to pass return values back up
			// the call chain
			c.statement=forStmt;
			
			return c;
		}
		return super.visit(node, data);
	}

	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTIfStatement, java.lang.Object)
	 */
	@Override
	public Object visit(ASTIfStatement node, Object data)
	{
		//
		// An if-statement in the AST has:
		// Expression (the condition)
		// ASTStatement (the body of the if; may be a BlockStatement)
		// ASTStatement (optional; the body of the else; will be 
		// ASTStatement => ASTIfStatement for chained if-else clauses
		//
		if (data instanceof GhettoStatementContainer) {
			GhettoStatementContainer c=(GhettoStatementContainer)data;
			
			// Extract the if condition
			IfStatementHeader ifCond=new IfStatementHeader(tokenSet);
			ConditionalStatementVisitor visitor=new ConditionalStatementVisitor(tokenSet);
			node.jjtAccept(visitor, ifCond);
			
			// get the body of the if stmt
			StatementList thenBody=new StatementList(tokenSet);
			thenBody.setStmtType(IF_BODY);
			// visit the first statement
			ASTStatement n=node.getFirstChildOfType(ASTStatement.class);
			n.jjtAccept(this, thenBody);

			// set the coordinates of the entire statement
			// set the header condition and the then-body
			IfStatement ifStmt=new IfStatement(tokenSet);
			ifStmt.setCoordinatesFromNode(node);
			ifStmt.setHeaderStmt(ifCond);
			ifStmt.setBody(thenBody);
            
			// set the else-body, if there is one
			if (node.jjtGetNumChildren() > 2) {
				StatementList elseBody=new StatementList(tokenSet);
				elseBody.setStmtType(ELSE_BODY);

				// recover the 'else' token
				//TokenSet preceedingTokens=
                  //  tokenSet.getPrecedingTokens((ASTStatement)node.jjtGetChild(2));
				Statement syntheticElse=
				    new SingleStatement(ELSE_KEYWORD, tokenSet);
				//SCToken elseToken=preceedingTokens.getLast();
				SCToken elseToken=tokenSet.getOnePrecedingToken((ASTStatement)node.jjtGetChild(2));
				syntheticElse.setCoordinatesFromToken(elseToken);
				// make the synthetic else statement the first statement of the else block
				elseBody.addStatement(syntheticElse);
				
				((ASTStatement)node.jjtGetChild(2)).jjtAccept(this, elseBody);
				ifStmt.setElseBody(elseBody);
			}
			
			// Using the ghetto statement container to pass return values back up
			// the call chain
			c.statement=ifStmt;
			
			return c;
		}
		return super.visit(node, data);
	}

	/* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTWhileStatement, java.lang.Object)
	 */
	@Override
	public Object visit(ASTWhileStatement node, Object data)
	{
		if (data instanceof GhettoStatementContainer) {
			GhettoStatementContainer c=(GhettoStatementContainer)data;
			
			// Extract the for statement header
			WhileStatementHeader whileHeader=new WhileStatementHeader(tokenSet);
			ConditionalStatementVisitor visitor=new ConditionalStatementVisitor(tokenSet);
			node.jjtAccept(visitor, whileHeader);
			
			// get the body of the while loop
			StatementList list=new StatementList(tokenSet);
			list.setStmtType(WHILE_BODY);
			super.visit(node, list);
			
			WhileStatement whileStmt=new WhileStatement(tokenSet);
			// compound while statement should have coordinates for the ENTIRE statement
			// (i.e. including the loop header and the loop body)
			whileStmt.setCoordinatesFromNode(node);
			whileStmt.setHeaderStmt(whileHeader);
			whileStmt.setBody(list);
			
			// Using the ghetto statement container to pass return values back up
			// the call chain
			c.statement=whileStmt;
			
			return c;
		}
		return super.visit(node, data);
	}

	/* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTDoStatement, java.lang.Object)
     */
    @Override
    public Object visit(ASTDoStatement node, Object data)
    {
        if (data instanceof GhettoStatementContainer) {
            GhettoStatementContainer c=(GhettoStatementContainer)data;
            
            // Extract the do statement header
            DoStatement doWhileLoop=new DoStatement(tokenSet);
            doWhileLoop.setCoordinatesFromNode(node);
            
            // get the "do" token
            SingleStatement doStmt=new SingleStatement(tokenSet);
            doStmt.setStmtType(DO_KEYWORD);
            doStmt.setCoordinatesFromToken(doWhileLoop.getTokensInRange().getFirst());
            doWhileLoop.setDoStatement(doStmt);
            
            // now extract the "header"
            WhileStatementHeader whileHeader=new WhileStatementHeader(tokenSet);
            // do-while loops are a little backwards, as the body statements comes before
            // the condition.  First, grab the condition, which will look like this:
            // while ( COND ) ;
            // though the coords we get only cover the COND part, so we'll need to expand by
            // two tokens before and two tokens afterwards.
            ASTExpression cond=node.getFirstChildOfType(ASTExpression.class);
            // set the range for just the "condition"
            // this is meta-information that could be used in the future
            MultiTokenCodeRange2D conditionRange=new MultiTokenCodeRange2D(tokenSet);
            conditionRange.setCoordinatesFromNode(cond);
            whileHeader.setCondition(conditionRange);
            // now set the coordinates for the whole while header, initially to just the condition
            whileHeader.setCoordinatesFromNode(cond);
            // grab the preceding "(" open paren 
            whileHeader.expandByOnePrecedingToken();
            // grab the preceding "while" token
            whileHeader.expandByOnePrecedingToken();
            // grab the ")" closing paren
            whileHeader.expandByOneFollowingToken();
            // grab the ";" ending the while part of the statement
            whileHeader.expandByOneFollowingToken();
            doWhileLoop.setHeaderStmt(whileHeader);
            
            // get the body of the while loop
            StatementList list=new StatementList(tokenSet);
            list.setStmtType(DO_WHILE_BODY);
            super.visit(node, list);
            doWhileLoop.setBody(list);
            
            // Using the ghetto statement container to pass return values back up
            // the call chain
            c.statement=doWhileLoop;
            
            return c;
        }
        return super.visit(node, data);
    }



    /* (non-Javadoc)
	 * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTMethodDeclaration, java.lang.Object)
	 */
	@Override
	public Object visit(ASTMethodDeclaration node, Object data)
	{
	    if (data instanceof ClassDeclaration) {
			ClassDeclaration classDecl=(ClassDeclaration)data;
			
			// visit method body for its StatementList
			StatementList methodBody=new StatementList(tokenSet);
			methodBody.setStmtType(METHOD_BODY);
			methodBody.setCoordinatesFromNode(node);
			node.childrenAccept(this, methodBody);
			
			// get the method signature
			MethodSignatureVisitor visitor=new MethodSignatureVisitor(tokenSet);
			MethodSignature methodSignature=(MethodSignature)node.jjtAccept(visitor, null);
			
			MethodDeclarationStatement methodDeclaration=new MethodDeclarationStatement(tokenSet);
			methodDeclaration.setCoordinatesFromNode(node);
			methodDeclaration.setHeaderStmt(methodSignature);
			methodDeclaration.setBody(methodBody);
			
			// add this completed method to our list of methods
			classDecl.addMethodDeclarationStatement(methodDeclaration);

			return classDecl;
		}
		return super.visit(node, data);
	}

    /* (non-Javadoc)
     * @see net.sourceforge.pmd.ast.JavaParserVisitorAdapter#visit(net.sourceforge.pmd.ast.ASTConstructorDeclaration, java.lang.Object)
     */
    @Override
    public Object visit(ASTConstructorDeclaration node, Object data)
    {
	    if (data instanceof ClassDeclaration) {
			ClassDeclaration classDecl=(ClassDeclaration)data;
			
			// visit method body for its StatementList
			StatementList methodBody=new StatementList(tokenSet);
			methodBody.setStmtType(METHOD_BODY);
			methodBody.setCoordinatesFromNode(node);
			node.childrenAccept(this, methodBody);
			
			// get the method signature
			MethodSignatureVisitor visitor=new MethodSignatureVisitor(tokenSet);
			MethodSignature methodSignature=(MethodSignature)node.jjtAccept(visitor, null);
			
			// ASTConstructorDeclaration doesn't have an ASTBlock, just a series of
            // ASTBlockStatements (or nothing for an empty constructor method body)
            // we need to find the first ASTBlockStatement (if any) and adjust the start 
            // of the method body as starting there
            ASTBlockStatement firstBodyStatement=node.getFirstChildOfType(ASTBlockStatement.class);
            if (firstBodyStatement!=null) {
                methodBody.setBeginCoordsFromNode(firstBodyStatement);
            } else {
                // otherwise, the body is empty, so set the body as starting
                // and ending wherever the method ends
                methodBody.setBeginLine(methodBody.getEndLine());
                methodBody.setBeginColumn(methodBody.getEndColumn());
            }
            
			MethodDeclarationStatement methodDeclaration=new MethodDeclarationStatement(tokenSet);
			methodDeclaration.setCoordinatesFromNode(node);
			methodDeclaration.setHeaderStmt(methodSignature);
			methodDeclaration.setBody(methodBody);
			
			// add this completed method to our list of methods
			classDecl.addMethodDeclarationStatement(methodDeclaration);

			return classDecl;
		}
	    return super.visit(node, data);
    }
    
    /**
     * Get the left-most grandchild (i.e. the 0th child of the 0th child)
     * of this node.  Returns null if no grandchild exists.
     * @param node
     * @return
     */
    private static Node getGrandChild(Node node) {
        if (node.jjtGetNumChildren()==0)
            return null;
        //SimpleJavaNode child=(SimpleJavaNode)node.jjtGetChild(0);
        Node child=node.jjtGetChild(0);
        if (child.jjtGetNumChildren()==0)
            return null;
        return child.jjtGetChild(0);
    }
	
}
